// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <memory>
#include <set>
#include <sstream>
#include <vector>

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/sys_info.h"
#include "base/test/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/public/test/mock_special_storage_policy.h"
#include "content/public/test/mock_storage_client.h"
#include "storage/browser/quota/quota_database.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using storage::kQuotaErrorAbort;
using storage::kQuotaErrorInvalidModification;
using storage::kQuotaErrorNotSupported;
using storage::kQuotaStatusOk;
using storage::kQuotaStatusUnknown;
using storage::kStorageTypePersistent;
using storage::kStorageTypeSyncable;
using storage::kStorageTypeTemporary;
using storage::kStorageTypeUnknown;
using storage::QuotaClient;
using storage::QuotaManager;
using storage::QuotaStatusCode;
using storage::StorageType;
using storage::UsageAndQuota;
using storage::UsageInfo;
using storage::UsageInfoEntries;

namespace content {

namespace {

    // For shorter names.
    const StorageType kTemp = kStorageTypeTemporary;
    const StorageType kPerm = kStorageTypePersistent;
    const StorageType kSync = kStorageTypeSyncable;

    const int kAllClients = QuotaClient::kAllClientsMask;

    const int64_t kAvailableSpaceForApp = 13377331U;

    const int64_t kMinimumPreserveForSystem = QuotaManager::kMinimumPreserveForSystem;
    const int kPerHostTemporaryPortion = QuotaManager::kPerHostTemporaryPortion;

    const GURL kTestEvictionOrigin = GURL("http://test.eviction.policy/result");

    // Returns a deterministic value for the amount of available disk space.
    int64_t GetAvailableDiskSpaceForTest()
    {
        return kAvailableSpaceForApp + kMinimumPreserveForSystem;
    }

    bool GetVolumeInfoForTests(const base::FilePath&,
        uint64_t* available, uint64_t* total)
    {
        *available = static_cast<uint64_t>(GetAvailableDiskSpaceForTest());
        *total = *available * 2;
        return true;
    }

    class TestEvictionPolicy : public storage::QuotaEvictionPolicy {
    public:
        TestEvictionPolicy() { }
        ~TestEvictionPolicy() override { }

        // Overridden from storage::QuotaEvictionPolicy:
        void GetEvictionOrigin(const scoped_refptr<storage::SpecialStoragePolicy>&
                                   special_storage_policy,
            const std::set<GURL>& exceptions,
            const std::map<GURL, int64_t>& usage_map,
            int64_t global_quota,
            const storage::GetOriginCallback& callback) override
        {
            callback.Run(kTestEvictionOrigin);
        }
    };

} // namespace

class QuotaManagerTest : public testing::Test {
protected:
    typedef QuotaManager::QuotaTableEntry QuotaTableEntry;
    typedef QuotaManager::QuotaTableEntries QuotaTableEntries;
    typedef QuotaManager::OriginInfoTableEntries OriginInfoTableEntries;

public:
    QuotaManagerTest()
        : mock_time_counter_(0)
        , weak_factory_(this)
    {
    }

    void SetUp() override
    {
        ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
        mock_special_storage_policy_ = new MockSpecialStoragePolicy;
        ResetQuotaManager(false /* is_incognito */);
    }

    void TearDown() override
    {
        // Make sure the quota manager cleans up correctly.
        quota_manager_ = NULL;
        base::RunLoop().RunUntilIdle();
    }

protected:
    void ResetQuotaManager(bool is_incognito)
    {
        quota_manager_ = new QuotaManager(is_incognito, data_dir_.GetPath(),
            base::ThreadTaskRunnerHandle::Get().get(),
            base::ThreadTaskRunnerHandle::Get().get(),
            mock_special_storage_policy_.get());
        // Don't (automatically) start the eviction for testing.
        quota_manager_->eviction_disabled_ = true;
        // Don't query the hard disk for remaining capacity.
        quota_manager_->get_volume_info_fn_ = &GetVolumeInfoForTests;
        additional_callback_count_ = 0;
    }

    MockStorageClient* CreateClient(
        const MockOriginData* mock_data,
        size_t mock_data_size,
        QuotaClient::ID id)
    {
        return new MockStorageClient(quota_manager_->proxy(),
            mock_data, id, mock_data_size);
    }

    void RegisterClient(MockStorageClient* client)
    {
        quota_manager_->proxy()->RegisterClient(client);
    }

    void GetUsageInfo()
    {
        usage_info_.clear();
        quota_manager_->GetUsageInfo(
            base::Bind(&QuotaManagerTest::DidGetUsageInfo,
                weak_factory_.GetWeakPtr()));
    }

    void GetUsageAndQuotaForWebApps(const GURL& origin,
        StorageType type)
    {
        quota_status_ = kQuotaStatusUnknown;
        usage_ = -1;
        quota_ = -1;
        quota_manager_->GetUsageAndQuotaForWebApps(
            origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota, weak_factory_.GetWeakPtr()));
    }

    void GetUsageAndQuotaForStorageClient(const GURL& origin,
        StorageType type)
    {
        quota_status_ = kQuotaStatusUnknown;
        usage_ = -1;
        quota_ = -1;
        quota_manager_->GetUsageAndQuota(
            origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota, weak_factory_.GetWeakPtr()));
    }

    void GetTemporaryGlobalQuota()
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_ = -1;
        quota_manager_->GetTemporaryGlobalQuota(
            base::Bind(&QuotaManagerTest::DidGetQuota,
                weak_factory_.GetWeakPtr()));
    }

    void SetTemporaryGlobalQuota(int64_t new_quota)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_ = -1;
        quota_manager_->SetTemporaryGlobalOverrideQuota(
            new_quota,
            base::Bind(&QuotaManagerTest::DidGetQuota,
                weak_factory_.GetWeakPtr()));
    }

    void GetPersistentHostQuota(const std::string& host)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_ = -1;
        quota_manager_->GetPersistentHostQuota(
            host,
            base::Bind(&QuotaManagerTest::DidGetHostQuota,
                weak_factory_.GetWeakPtr()));
    }

    void SetPersistentHostQuota(const std::string& host, int64_t new_quota)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_ = -1;
        quota_manager_->SetPersistentHostQuota(
            host, new_quota,
            base::Bind(&QuotaManagerTest::DidGetHostQuota,
                weak_factory_.GetWeakPtr()));
    }

    void GetGlobalUsage(StorageType type)
    {
        usage_ = -1;
        unlimited_usage_ = -1;
        quota_manager_->GetGlobalUsage(
            type,
            base::Bind(&QuotaManagerTest::DidGetGlobalUsage,
                weak_factory_.GetWeakPtr()));
    }

    void GetHostUsage(const std::string& host, StorageType type)
    {
        usage_ = -1;
        quota_manager_->GetHostUsage(
            host, type,
            base::Bind(&QuotaManagerTest::DidGetHostUsage,
                weak_factory_.GetWeakPtr()));
    }

    void RunAdditionalUsageAndQuotaTask(const GURL& origin, StorageType type)
    {
        quota_manager_->GetUsageAndQuota(
            origin, type,
            base::Bind(&QuotaManagerTest::DidGetUsageAndQuotaAdditional,
                weak_factory_.GetWeakPtr()));
    }

    void DeleteClientOriginData(QuotaClient* client,
        const GURL& origin,
        StorageType type)
    {
        DCHECK(client);
        quota_status_ = kQuotaStatusUnknown;
        client->DeleteOriginData(
            origin, type,
            base::Bind(&QuotaManagerTest::StatusCallback,
                weak_factory_.GetWeakPtr()));
    }

    void EvictOriginData(const GURL& origin,
        StorageType type)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_manager_->EvictOriginData(
            origin, type,
            base::Bind(&QuotaManagerTest::StatusCallback,
                weak_factory_.GetWeakPtr()));
    }

    void DeleteOriginData(const GURL& origin,
        StorageType type,
        int quota_client_mask)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_manager_->DeleteOriginData(
            origin, type, quota_client_mask,
            base::Bind(&QuotaManagerTest::StatusCallback,
                weak_factory_.GetWeakPtr()));
    }

    void DeleteHostData(const std::string& host,
        StorageType type,
        int quota_client_mask)
    {
        quota_status_ = kQuotaStatusUnknown;
        quota_manager_->DeleteHostData(
            host, type, quota_client_mask,
            base::Bind(&QuotaManagerTest::StatusCallback,
                weak_factory_.GetWeakPtr()));
    }

    void GetAvailableSpace()
    {
        quota_status_ = kQuotaStatusUnknown;
        available_space_ = -1;
        quota_manager_->GetAvailableSpace(
            base::Bind(&QuotaManagerTest::DidGetAvailableSpace,
                weak_factory_.GetWeakPtr()));
    }

    void GetUsageAndQuotaForEviction()
    {
        quota_status_ = kQuotaStatusUnknown;
        usage_ = -1;
        unlimited_usage_ = -1;
        quota_ = -1;
        available_space_ = -1;
        quota_manager_->GetUsageAndQuotaForEviction(
            base::Bind(&QuotaManagerTest::DidGetUsageAndQuotaForEviction,
                weak_factory_.GetWeakPtr()));
    }

    void GetCachedOrigins(StorageType type, std::set<GURL>* origins)
    {
        ASSERT_TRUE(origins != NULL);
        origins->clear();
        quota_manager_->GetCachedOrigins(type, origins);
    }

    bool GetVolumeInfo(const base::FilePath& path,
        uint64_t* available_space,
        uint64_t* total_size)
    {
        return QuotaManager::GetVolumeInfo(path, available_space, total_size);
    }

    void NotifyStorageAccessed(QuotaClient* client,
        const GURL& origin,
        StorageType type)
    {
        DCHECK(client);
        quota_manager_->NotifyStorageAccessedInternal(
            client->id(), origin, type, IncrementMockTime());
    }

    void DeleteOriginFromDatabase(const GURL& origin, StorageType type)
    {
        quota_manager_->DeleteOriginFromDatabase(origin, type, false);
    }

    void GetEvictionOrigin(StorageType type)
    {
        eviction_origin_ = GURL();
        // The quota manager's default eviction policy is to use an LRU eviction
        // policy.
        quota_manager_->GetEvictionOrigin(
            type, std::set<GURL>(), 0,
            base::Bind(&QuotaManagerTest::DidGetEvictionOrigin,
                weak_factory_.GetWeakPtr()));
    }

    void NotifyOriginInUse(const GURL& origin)
    {
        quota_manager_->NotifyOriginInUse(origin);
    }

    void NotifyOriginNoLongerInUse(const GURL& origin)
    {
        quota_manager_->NotifyOriginNoLongerInUse(origin);
    }

    void GetOriginsModifiedSince(StorageType type, base::Time modified_since)
    {
        modified_origins_.clear();
        modified_origins_type_ = kStorageTypeUnknown;
        quota_manager_->GetOriginsModifiedSince(
            type, modified_since,
            base::Bind(&QuotaManagerTest::DidGetModifiedOrigins,
                weak_factory_.GetWeakPtr()));
    }

    void DumpQuotaTable()
    {
        quota_entries_.clear();
        quota_manager_->DumpQuotaTable(
            base::Bind(&QuotaManagerTest::DidDumpQuotaTable,
                weak_factory_.GetWeakPtr()));
    }

    void DumpOriginInfoTable()
    {
        origin_info_entries_.clear();
        quota_manager_->DumpOriginInfoTable(
            base::Bind(&QuotaManagerTest::DidDumpOriginInfoTable,
                weak_factory_.GetWeakPtr()));
    }

    void DidGetUsageInfo(const UsageInfoEntries& entries)
    {
        usage_info_.insert(usage_info_.begin(), entries.begin(), entries.end());
    }

    void DidGetUsageAndQuota(QuotaStatusCode status,
        int64_t usage,
        int64_t quota)
    {
        quota_status_ = status;
        usage_ = usage;
        quota_ = quota;
    }

    void DidGetQuota(QuotaStatusCode status, int64_t quota)
    {
        quota_status_ = status;
        quota_ = quota;
    }

    void DidGetAvailableSpace(QuotaStatusCode status, int64_t available_space)
    {
        quota_status_ = status;
        available_space_ = available_space;
    }

    void DidGetHostQuota(QuotaStatusCode status, int64_t quota)
    {
        quota_status_ = status;
        quota_ = quota;
    }

    void DidGetGlobalUsage(int64_t usage, int64_t unlimited_usage)
    {
        usage_ = usage;
        unlimited_usage_ = unlimited_usage;
    }

    void DidGetHostUsage(int64_t usage) { usage_ = usage; }

    void StatusCallback(QuotaStatusCode status)
    {
        ++status_callback_count_;
        quota_status_ = status;
    }

    void DidGetUsageAndQuotaForEviction(QuotaStatusCode status,
        const UsageAndQuota& usage_and_quota)
    {
        quota_status_ = status;
        limited_usage_ = usage_and_quota.global_limited_usage;
        quota_ = usage_and_quota.quota;
        available_space_ = usage_and_quota.available_disk_space;
    }

    void DidGetEvictionOrigin(const GURL& origin)
    {
        eviction_origin_ = origin;
    }

    void DidGetModifiedOrigins(const std::set<GURL>& origins, StorageType type)
    {
        modified_origins_ = origins;
        modified_origins_type_ = type;
    }

    void DidDumpQuotaTable(const QuotaTableEntries& entries)
    {
        quota_entries_ = entries;
    }

    void DidDumpOriginInfoTable(const OriginInfoTableEntries& entries)
    {
        origin_info_entries_ = entries;
    }

    void GetUsage_WithModifyTestBody(const StorageType type);

    void set_additional_callback_count(int c) { additional_callback_count_ = c; }
    int additional_callback_count() const { return additional_callback_count_; }
    void DidGetUsageAndQuotaAdditional(QuotaStatusCode status,
        int64_t usage,
        int64_t quota)
    {
        ++additional_callback_count_;
    }

    QuotaManager* quota_manager() const { return quota_manager_.get(); }
    void set_quota_manager(QuotaManager* quota_manager)
    {
        quota_manager_ = quota_manager;
    }

    MockSpecialStoragePolicy* mock_special_storage_policy() const
    {
        return mock_special_storage_policy_.get();
    }

    QuotaStatusCode status() const { return quota_status_; }
    const UsageInfoEntries& usage_info() const { return usage_info_; }
    int64_t usage() const { return usage_; }
    int64_t limited_usage() const { return limited_usage_; }
    int64_t unlimited_usage() const { return unlimited_usage_; }
    int64_t quota() const { return quota_; }
    int64_t available_space() const { return available_space_; }
    const GURL& eviction_origin() const { return eviction_origin_; }
    const std::set<GURL>& modified_origins() const { return modified_origins_; }
    StorageType modified_origins_type() const { return modified_origins_type_; }
    const QuotaTableEntries& quota_entries() const { return quota_entries_; }
    const OriginInfoTableEntries& origin_info_entries() const
    {
        return origin_info_entries_;
    }
    base::FilePath profile_path() const { return data_dir_.GetPath(); }
    int status_callback_count() const { return status_callback_count_; }
    void reset_status_callback_count() { status_callback_count_ = 0; }

private:
    base::Time IncrementMockTime()
    {
        ++mock_time_counter_;
        return base::Time::FromDoubleT(mock_time_counter_ * 10.0);
    }

    base::MessageLoop message_loop_;
    base::ScopedTempDir data_dir_;

    scoped_refptr<QuotaManager> quota_manager_;
    scoped_refptr<MockSpecialStoragePolicy> mock_special_storage_policy_;

    QuotaStatusCode quota_status_;
    UsageInfoEntries usage_info_;
    int64_t usage_;
    int64_t limited_usage_;
    int64_t unlimited_usage_;
    int64_t quota_;
    int64_t available_space_;
    GURL eviction_origin_;
    std::set<GURL> modified_origins_;
    StorageType modified_origins_type_;
    QuotaTableEntries quota_entries_;
    OriginInfoTableEntries origin_info_entries_;
    int status_callback_count_;

    int additional_callback_count_;

    int mock_time_counter_;

    base::WeakPtrFactory<QuotaManagerTest> weak_factory_;

    DISALLOW_COPY_AND_ASSIGN(QuotaManagerTest);
};

TEST_F(QuotaManagerTest, GetUsageInfo)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com:8080/", kTemp, 15 },
        { "http://bar.com/", kTemp, 20 },
        { "http://bar.com/", kPerm, 50 },
    };
    static const MockOriginData kData2[] = {
        { "https://foo.com/", kTemp, 30 },
        { "https://foo.com:8081/", kTemp, 35 },
        { "http://bar.com/", kPerm, 40 },
        { "http://example.com/", kPerm, 40 },
    };
    RegisterClient(CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem));
    RegisterClient(CreateClient(kData2, arraysize(kData2),
        QuotaClient::kDatabase));

    GetUsageInfo();
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(4U, usage_info().size());
    for (size_t i = 0; i < usage_info().size(); ++i) {
        const UsageInfo& info = usage_info()[i];
        if (info.host == "foo.com" && info.type == kTemp) {
            EXPECT_EQ(10 + 15 + 30 + 35, info.usage);
        } else if (info.host == "bar.com" && info.type == kTemp) {
            EXPECT_EQ(20, info.usage);
        } else if (info.host == "bar.com" && info.type == kPerm) {
            EXPECT_EQ(50 + 40, info.usage);
        } else if (info.host == "example.com" && info.type == kPerm) {
            EXPECT_EQ(40, info.usage);
        } else {
            ADD_FAILURE()
                << "Unexpected host, type: " << info.host << ", " << info.type;
        }
    }
}

TEST_F(QuotaManagerTest, GetUsageAndQuota_Simple)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com/", kPerm, 80 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(80, usage());
    EXPECT_EQ(0, quota());

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_LE(0, quota());
    int64_t quota_returned_for_foo = quota();

    GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(quota_returned_for_foo, quota());
}

TEST_F(QuotaManagerTest, GetUsage_NoClient)
{
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());
    EXPECT_EQ(0, unlimited_usage());

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());
    EXPECT_EQ(0, unlimited_usage());
}

TEST_F(QuotaManagerTest, GetUsage_EmptyClient)
{
    RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());
    EXPECT_EQ(0, unlimited_usage());

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());
    EXPECT_EQ(0, unlimited_usage());
}

TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com:8080/", kTemp, 20 },
        { "http://bar.com/", kTemp, 5 },
        { "https://bar.com/", kTemp, 7 },
        { "http://baz.com/", kTemp, 30 },
        { "http://foo.com/", kPerm, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));

    // This time explicitly sets a temporary global quota.
    SetTemporaryGlobalQuota(100);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(100, quota());

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());

    const int kPerHostQuota = 100 / kPerHostTemporaryPortion;

    // The host's quota should be its full portion of the global quota
    // since global usage is under the global quota.
    EXPECT_EQ(kPerHostQuota, quota());

    GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(5 + 7, usage());
    EXPECT_EQ(kPerHostQuota, quota());
}

TEST_F(QuotaManagerTest, GetUsage_MultipleClients)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://bar.com/", kTemp, 2 },
        { "http://bar.com/", kPerm, 4 },
        { "http://unlimited/", kPerm, 8 },
        { "http://installed/", kPerm, 16 },
    };
    static const MockOriginData kData2[] = {
        { "https://foo.com/", kTemp, 128 },
        { "http://example.com/", kPerm, 256 },
        { "http://unlimited/", kTemp, 512 },
        { "http://installed/", kTemp, 1024 },
    };
    mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
    mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/"));
    RegisterClient(CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem));
    RegisterClient(CreateClient(kData2, arraysize(kData2),
        QuotaClient::kDatabase));

    const int64_t kTempQuotaBase = GetAvailableDiskSpaceForTest() / kPerHostTemporaryPortion;

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(1 + 128, usage());

    GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4, usage());

    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(512, usage());
    EXPECT_EQ(std::min(kAvailableSpaceForApp, kTempQuotaBase) + usage(), quota());

    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(8, usage());
    EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());

    GetAvailableSpace();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_LE(0, available_space());

    GetUsageAndQuotaForWebApps(GURL("http://installed/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(1024, usage());
    EXPECT_EQ(std::min(kAvailableSpaceForApp, kTempQuotaBase) + usage(), quota());

    GetUsageAndQuotaForWebApps(GURL("http://installed/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(16, usage());
    EXPECT_EQ(usage(), quota()); // Over-budget case.

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(1 + 2 + 128 + 512 + 1024, usage());
    EXPECT_EQ(512, unlimited_usage());

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4 + 8 + 16 + 256, usage());
    EXPECT_EQ(8, unlimited_usage());
}

void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type)
{
    const MockOriginData data[] = {
        { "http://foo.com/", type, 10 },
        { "http://foo.com:1/", type, 20 },
    };
    MockStorageClient* client = CreateClient(data, arraysize(data),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());

    client->ModifyOriginAndNotify(GURL("http://foo.com/"), type, 30);
    client->ModifyOriginAndNotify(GURL("http://foo.com:1/"), type, -5);
    client->AddOriginAndNotify(GURL("https://foo.com/"), type, 1);

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20 + 30 - 5 + 1, usage());
    int foo_usage = usage();

    client->AddOriginAndNotify(GURL("http://bar.com/"), type, 40);
    GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), type);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(40, usage());

    GetGlobalUsage(type);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(foo_usage + 40, usage());
    EXPECT_EQ(0, unlimited_usage());
}

TEST_F(QuotaManagerTest, GetTemporaryUsage_WithModify)
{
    GetUsage_WithModifyTestBody(kTemp);
}

TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com:8080/", kTemp, 20 },
        { "http://bar.com/", kTemp, 13 },
        { "http://foo.com/", kPerm, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));
    SetTemporaryGlobalQuota(100);
    base::RunLoop().RunUntilIdle();

    const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion;

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());
    EXPECT_EQ(kPerHostQuota, quota());

    set_additional_callback_count(0);
    RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
        kTemp);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());
    EXPECT_EQ(kPerHostQuota, quota());
    EXPECT_EQ(2, additional_callback_count());
}

TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_NukeManager)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com:8080/", kTemp, 20 },
        { "http://bar.com/", kTemp, 13 },
        { "http://foo.com/", kPerm, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));
    SetTemporaryGlobalQuota(100);
    base::RunLoop().RunUntilIdle();

    set_additional_callback_count(0);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
        kTemp);
    RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"),
        kTemp);

    DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
    DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);

    // Nuke before waiting for callbacks.
    set_quota_manager(NULL);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaErrorAbort, status());
}

TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Overbudget)
{
    static const MockOriginData kData[] = {
        { "http://usage1/", kTemp, 1 },
        { "http://usage10/", kTemp, 10 },
        { "http://usage200/", kTemp, 200 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));
    SetTemporaryGlobalQuota(100);
    base::RunLoop().RunUntilIdle();

    const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion;

    GetUsageAndQuotaForWebApps(GURL("http://usage1/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(1, usage());
    EXPECT_EQ(1, quota()); // should be clamped to our current usage

    GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_EQ(10, quota());

    GetUsageAndQuotaForWebApps(GURL("http://usage200/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(200, usage());
    EXPECT_EQ(kPerHostQuota, quota()); // should be clamped to the nominal quota
}

TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Unlimited)
{
    static const MockOriginData kData[] = {
        { "http://usage10/", kTemp, 10 },
        { "http://usage50/", kTemp, 50 },
        { "http://unlimited/", kTemp, 4000 },
    };
    mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    // Test when not overbugdet.
    SetTemporaryGlobalQuota(1000);
    base::RunLoop().RunUntilIdle();

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(10 + 50 + 4000, usage());
    EXPECT_EQ(4000, unlimited_usage());

    const int kPerHostQuotaFor1000 = 1000 / QuotaManager::kPerHostTemporaryPortion;

    GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_EQ(kPerHostQuotaFor1000, quota());

    GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(50, usage());
    EXPECT_EQ(kPerHostQuotaFor1000, quota());

    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4000, usage());
    EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());

    GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(QuotaManager::kNoLimit, quota());

    // Test when overbugdet.
    SetTemporaryGlobalQuota(100);
    base::RunLoop().RunUntilIdle();

    const int kPerHostQuotaFor100 = 100 / QuotaManager::kPerHostTemporaryPortion;

    GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_EQ(kPerHostQuotaFor100, quota());

    GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(50, usage());
    EXPECT_EQ(kPerHostQuotaFor100, quota());

    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4000, usage());
    EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());

    GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(QuotaManager::kNoLimit, quota());

    // Revoke the unlimited rights and make sure the change is noticed.
    mock_special_storage_policy()->Reset();
    mock_special_storage_policy()->NotifyCleared();

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(10 + 50 + 4000, usage());
    EXPECT_EQ(0, unlimited_usage());

    GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_EQ(10, quota()); // should be clamped to our current usage

    GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(50, usage());
    EXPECT_EQ(kPerHostQuotaFor100, quota());

    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4000, usage());
    EXPECT_EQ(kPerHostQuotaFor100, quota());

    GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(4000, usage());
    EXPECT_EQ(kPerHostQuotaFor100, quota());
}

TEST_F(QuotaManagerTest, OriginInUse)
{
    const GURL kFooOrigin("http://foo.com/");
    const GURL kBarOrigin("http://bar.com/");

    EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin));
    quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 1
    EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));
    quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 2
    EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));
    quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin); // count of 1
    EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin));

    EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin));
    quota_manager()->NotifyOriginInUse(kBarOrigin);
    EXPECT_TRUE(quota_manager()->IsOriginInUse(kBarOrigin));
    quota_manager()->NotifyOriginNoLongerInUse(kBarOrigin);
    EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin));

    quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin);
    EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin));
}

TEST_F(QuotaManagerTest, GetAndSetPerststentHostQuota)
{
    RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));

    GetPersistentHostQuota("foo.com");
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, quota());

    SetPersistentHostQuota("foo.com", 100);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(100, quota());

    GetPersistentHostQuota("foo.com");
    SetPersistentHostQuota("foo.com", 200);
    GetPersistentHostQuota("foo.com");
    SetPersistentHostQuota("foo.com", QuotaManager::kPerHostPersistentQuotaLimit);
    GetPersistentHostQuota("foo.com");
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(QuotaManager::kPerHostPersistentQuotaLimit, quota());

    // Persistent quota should be capped at the per-host quota limit.
    SetPersistentHostQuota("foo.com",
        QuotaManager::kPerHostPersistentQuotaLimit + 100);
    GetPersistentHostQuota("foo.com");
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(QuotaManager::kPerHostPersistentQuotaLimit, quota());
}

TEST_F(QuotaManagerTest, GetAndSetPersistentUsageAndQuota)
{
    RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(0, quota());

    SetPersistentHostQuota("foo.com", 100);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(100, quota());

    // For installed app GetUsageAndQuotaForWebApps returns the capped quota.
    mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/"));
    SetPersistentHostQuota("installed", kAvailableSpaceForApp + 100);
    GetUsageAndQuotaForWebApps(GURL("http://installed/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kAvailableSpaceForApp, quota());

    // Ditto for unlimited apps.
    mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
    GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kAvailableSpaceForApp, quota());

    // GetUsageAndQuotaForStorageClient should just return 0 usage and
    // kNoLimit quota.
    GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, usage());
    EXPECT_EQ(QuotaManager::kNoLimit, quota());
}

TEST_F(QuotaManagerTest, GetSyncableQuota)
{
    RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem));

    // Pre-condition check: available disk space (for testing) is less than
    // the default quota for syncable storage.
    EXPECT_LE(kAvailableSpaceForApp,
        QuotaManager::kSyncableStorageDefaultHostQuota);

    // For installed apps the quota manager should return
    // kAvailableSpaceForApp as syncable quota (because of the pre-condition).
    mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/"));
    GetUsageAndQuotaForWebApps(GURL("http://installed/"), kSync);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(kAvailableSpaceForApp, quota());

    // If it's not installed (which shouldn't happen in real case) it
    // should just return the default host quota for syncable.
    GetUsageAndQuotaForWebApps(GURL("http://foo/"), kSync);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, usage());
    EXPECT_EQ(QuotaManager::kSyncableStorageDefaultHostQuota, quota());
}

TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_MultiOrigins)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kPerm, 10 },
        { "http://foo.com:8080/", kPerm, 20 },
        { "https://foo.com/", kPerm, 13 },
        { "https://foo.com:8081/", kPerm, 19 },
        { "http://bar.com/", kPerm, 5 },
        { "https://bar.com/", kPerm, 7 },
        { "http://baz.com/", kPerm, 30 },
        { "http://foo.com/", kTemp, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));

    SetPersistentHostQuota("foo.com", 100);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20 + 13 + 19, usage());
    EXPECT_EQ(100, quota());
}

TEST_F(QuotaManagerTest, GetPersistentUsage_WithModify)
{
    GetUsage_WithModifyTestBody(kPerm);
}

TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_WithAdditionalTasks)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kPerm, 10 },
        { "http://foo.com:8080/", kPerm, 20 },
        { "http://bar.com/", kPerm, 13 },
        { "http://foo.com/", kTemp, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));
    SetPersistentHostQuota("foo.com", 100);

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());
    EXPECT_EQ(100, quota());

    set_additional_callback_count(0);
    RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
        kPerm);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10 + 20, usage());
    EXPECT_EQ(2, additional_callback_count());
}

TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_NukeManager)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kPerm, 10 },
        { "http://foo.com:8080/", kPerm, 20 },
        { "http://bar.com/", kPerm, 13 },
        { "http://foo.com/", kTemp, 40 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));
    SetPersistentHostQuota("foo.com", 100);

    set_additional_callback_count(0);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), kPerm);
    RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);

    // Nuke before waiting for callbacks.
    set_quota_manager(NULL);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaErrorAbort, status());
}

TEST_F(QuotaManagerTest, GetUsage_Simple)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kPerm, 1 },
        { "http://foo.com:1/", kPerm, 20 },
        { "http://bar.com/", kTemp, 300 },
        { "https://buz.com/", kTemp, 4000 },
        { "http://buz.com/", kTemp, 50000 },
        { "http://bar.com:1/", kPerm, 600000 },
        { "http://foo.com/", kTemp, 7000000 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 1 + 20 + 600000);
    EXPECT_EQ(0, unlimited_usage());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000);
    EXPECT_EQ(0, unlimited_usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 1 + 20);

    GetHostUsage("buz.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 4000 + 50000);
}

TEST_F(QuotaManagerTest, GetUsage_WithModification)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kPerm, 1 },
        { "http://foo.com:1/", kPerm, 20 },
        { "http://bar.com/", kTemp, 300 },
        { "https://buz.com/", kTemp, 4000 },
        { "http://buz.com/", kTemp, 50000 },
        { "http://bar.com:1/", kPerm, 600000 },
        { "http://foo.com/", kTemp, 7000000 },
    };

    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 1 + 20 + 600000);
    EXPECT_EQ(0, unlimited_usage());

    client->ModifyOriginAndNotify(
        GURL("http://foo.com/"), kPerm, 80000000);

    GetGlobalUsage(kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 1 + 20 + 600000 + 80000000);
    EXPECT_EQ(0, unlimited_usage());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000);
    EXPECT_EQ(0, unlimited_usage());

    client->ModifyOriginAndNotify(
        GURL("http://foo.com/"), kTemp, 1);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000 + 1);
    EXPECT_EQ(0, unlimited_usage());

    GetHostUsage("buz.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 4000 + 50000);

    client->ModifyOriginAndNotify(
        GURL("http://buz.com/"), kTemp, 900000000);

    GetHostUsage("buz.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(usage(), 4000 + 50000 + 900000000);
}

TEST_F(QuotaManagerTest, GetUsage_WithDeleteOrigin)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://bar.com/", kTemp, 4000 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_pers = usage();

    DeleteClientOriginData(client, GURL("http://foo.com/"),
        kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp - 1, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_tmp - 1, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_pers, usage());
}

TEST_F(QuotaManagerTest, GetAvailableSpaceTest)
{
    GetAvailableSpace();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_LE(0, available_space());
}

TEST_F(QuotaManagerTest, SetTemporaryStorageEvictionPolicy)
{
    quota_manager()->SetTemporaryStorageEvictionPolicy(
        base::WrapUnique(new TestEvictionPolicy));

    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kTestEvictionOrigin, eviction_origin());
}

TEST_F(QuotaManagerTest, EvictOriginData)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://bar.com/", kTemp, 4000 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com/", kTemp, 50000 },
        { "http://foo.com:1/", kTemp, 6000 },
        { "http://foo.com/", kPerm, 700 },
        { "https://foo.com/", kTemp, 80 },
        { "http://bar.com/", kTemp, 9 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kDatabase);
    RegisterClient(client1);
    RegisterClient(client2);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_pers = usage();

    for (size_t i = 0; i < arraysize(kData1); ++i)
        quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
            GURL(kData1[i].origin), kData1[i].type);
    for (size_t i = 0; i < arraysize(kData2); ++i)
        quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
            GURL(kData2[i].origin), kData2[i].type);
    base::RunLoop().RunUntilIdle();

    EvictOriginData(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();

    DumpOriginInfoTable();
    base::RunLoop().RunUntilIdle();

    typedef OriginInfoTableEntries::const_iterator iterator;
    for (iterator itr(origin_info_entries().begin()),
         end(origin_info_entries().end());
         itr != end; ++itr) {
        if (itr->type == kTemp)
            EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
    }

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp - (1 + 50000), usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_tmp - (1 + 50000), usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_pers, usage());
}

TEST_F(QuotaManagerTest, EvictOriginDataHistogram)
{
    const GURL kOrigin = GURL("http://foo.com/");
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 1 },
    };

    base::HistogramTester histograms;
    MockStorageClient* client = CreateClient(kData, arraysize(kData), QuotaClient::kFileSystem);
    RegisterClient(client);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();

    EvictOriginData(kOrigin, kTemp);
    base::RunLoop().RunUntilIdle();

    // Ensure used count and time since access are recorded.
    histograms.ExpectTotalCount(
        QuotaManager::kEvictedOriginAccessedCountHistogram, 1);
    histograms.ExpectBucketCount(
        QuotaManager::kEvictedOriginAccessedCountHistogram, 0, 1);
    histograms.ExpectTotalCount(
        QuotaManager::kEvictedOriginTimeSinceAccessHistogram, 1);

    // First eviction has no 'last' time to compare to.
    histograms.ExpectTotalCount(
        QuotaManager::kTimeBetweenRepeatedOriginEvictionsHistogram, 0);

    client->AddOriginAndNotify(kOrigin, kTemp, 100);

    // Change the used count of the origin.
    quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, GURL(kOrigin),
        kTemp);
    base::RunLoop().RunUntilIdle();

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();

    EvictOriginData(kOrigin, kTemp);
    base::RunLoop().RunUntilIdle();

    // The new used count should be logged.
    histograms.ExpectTotalCount(
        QuotaManager::kEvictedOriginAccessedCountHistogram, 2);
    histograms.ExpectBucketCount(
        QuotaManager::kEvictedOriginAccessedCountHistogram, 1, 1);
    histograms.ExpectTotalCount(
        QuotaManager::kEvictedOriginTimeSinceAccessHistogram, 2);

    // Second eviction should log a 'time between repeated eviction' sample.
    histograms.ExpectTotalCount(
        QuotaManager::kTimeBetweenRepeatedOriginEvictionsHistogram, 1);

    client->AddOriginAndNotify(kOrigin, kTemp, 100);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();

    DeleteOriginFromDatabase(kOrigin, kTemp);

    // Deletion from non-eviction source should not log a histogram sample.
    histograms.ExpectTotalCount(
        QuotaManager::kTimeBetweenRepeatedOriginEvictionsHistogram, 1);
}

TEST_F(QuotaManagerTest, EvictOriginDataWithDeletionError)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://bar.com/", kTemp, 4000 },
    };
    static const int kNumberOfTemporaryOrigins = 3;
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_pers = usage();

    for (size_t i = 0; i < arraysize(kData); ++i)
        NotifyStorageAccessed(client, GURL(kData[i].origin), kData[i].type);
    base::RunLoop().RunUntilIdle();

    client->AddOriginToErrorSet(GURL("http://foo.com/"), kTemp);

    for (int i = 0;
         i < QuotaManager::kThresholdOfErrorsToBeBlacklisted + 1;
         ++i) {
        EvictOriginData(GURL("http://foo.com/"), kTemp);
        base::RunLoop().RunUntilIdle();
        EXPECT_EQ(kQuotaErrorInvalidModification, status());
    }

    DumpOriginInfoTable();
    base::RunLoop().RunUntilIdle();

    bool found_origin_in_database = false;
    typedef OriginInfoTableEntries::const_iterator iterator;
    for (iterator itr(origin_info_entries().begin()),
         end(origin_info_entries().end());
         itr != end; ++itr) {
        if (itr->type == kTemp && itr->origin == "http://foo.com/") {
            found_origin_in_database = true;
            break;
        }
    }
    // The origin "http://foo.com/" should be in the database.
    EXPECT_TRUE(found_origin_in_database);

    for (size_t i = 0; i < kNumberOfTemporaryOrigins - 1; ++i) {
        GetEvictionOrigin(kTemp);
        base::RunLoop().RunUntilIdle();
        EXPECT_FALSE(eviction_origin().is_empty());
        // The origin "http://foo.com/" should not be in the LRU list.
        EXPECT_NE(std::string("http://foo.com/"), eviction_origin().spec());
        DeleteOriginFromDatabase(eviction_origin(), kTemp);
        base::RunLoop().RunUntilIdle();
    }

    // Now the LRU list must be empty.
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(eviction_origin().is_empty());

    // Deleting origins from the database should not affect the results of the
    // following checks.
    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_tmp, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_pers, usage());
}

TEST_F(QuotaManagerTest, GetUsageAndQuotaForEviction)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://unlimited/", kTemp, 4000 },
    };

    mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    SetTemporaryGlobalQuota(10000000);
    base::RunLoop().RunUntilIdle();

    GetUsageAndQuotaForEviction();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(21, limited_usage());
    EXPECT_EQ(10000000, quota());
    EXPECT_LE(0, available_space());
}

TEST_F(QuotaManagerTest, DeleteHostDataSimple)
{
    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 1 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    int64_t predelete_host_pers = usage();

    DeleteHostData(std::string(), kTemp, kAllClients);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_tmp, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_pers, usage());

    DeleteHostData("foo.com", kTemp, kAllClients);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp - 1, usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_tmp - 1, usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_host_pers, usage());
}

TEST_F(QuotaManagerTest, DeleteHostDataMultiple)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://bar.com/", kTemp, 4000 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com/", kTemp, 50000 },
        { "http://foo.com:1/", kTemp, 6000 },
        { "http://foo.com/", kPerm, 700 },
        { "https://foo.com/", kTemp, 80 },
        { "http://bar.com/", kTemp, 9 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kDatabase);
    RegisterClient(client1);
    RegisterClient(client2);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    GetHostUsage("bar.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_bar_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_pers = usage();

    GetHostUsage("bar.com", kPerm);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_bar_pers = usage();

    reset_status_callback_count();
    DeleteHostData("foo.com", kTemp, kAllClients);
    DeleteHostData("bar.com", kTemp, kAllClients);
    DeleteHostData("foo.com", kTemp, kAllClients);
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(3, status_callback_count());

    DumpOriginInfoTable();
    base::RunLoop().RunUntilIdle();

    typedef OriginInfoTableEntries::const_iterator iterator;
    for (iterator itr(origin_info_entries().begin()),
         end(origin_info_entries().end());
         itr != end; ++itr) {
        if (itr->type == kTemp) {
            EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
            EXPECT_NE(std::string("http://foo.com:1/"), itr->origin.spec());
            EXPECT_NE(std::string("https://foo.com/"), itr->origin.spec());
            EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec());
        }
    }

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp - (1 + 20 + 4000 + 50000 + 6000 + 80 + 9),
        usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - (1 + 20 + 50000 + 6000 + 80), usage());

    GetHostUsage("bar.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_pers, usage());

    GetHostUsage("bar.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_bar_pers, usage());
}

// Single-run DeleteOriginData cases must be well covered by
// EvictOriginData tests.
TEST_F(QuotaManagerTest, DeleteOriginDataMultiple)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
        { "http://foo.com:1/", kTemp, 20 },
        { "http://foo.com/", kPerm, 300 },
        { "http://bar.com/", kTemp, 4000 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com/", kTemp, 50000 },
        { "http://foo.com:1/", kTemp, 6000 },
        { "http://foo.com/", kPerm, 700 },
        { "https://foo.com/", kTemp, 80 },
        { "http://bar.com/", kTemp, 9 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kDatabase);
    RegisterClient(client1);
    RegisterClient(client2);

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_global_tmp = usage();

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    GetHostUsage("bar.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_bar_tmp = usage();

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_pers = usage();

    GetHostUsage("bar.com", kPerm);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_bar_pers = usage();

    for (size_t i = 0; i < arraysize(kData1); ++i)
        quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
            GURL(kData1[i].origin), kData1[i].type);
    for (size_t i = 0; i < arraysize(kData2); ++i)
        quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
            GURL(kData2[i].origin), kData2[i].type);
    base::RunLoop().RunUntilIdle();

    reset_status_callback_count();
    DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
    DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);
    DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(3, status_callback_count());

    DumpOriginInfoTable();
    base::RunLoop().RunUntilIdle();

    typedef OriginInfoTableEntries::const_iterator iterator;
    for (iterator itr(origin_info_entries().begin()),
         end(origin_info_entries().end());
         itr != end; ++itr) {
        if (itr->type == kTemp) {
            EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec());
            EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec());
        }
    }

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_global_tmp - (1 + 4000 + 50000 + 9), usage());

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - (1 + 50000), usage());

    GetHostUsage("bar.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage());

    GetHostUsage("foo.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_pers, usage());

    GetHostUsage("bar.com", kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_bar_pers, usage());
}

TEST_F(QuotaManagerTest, GetCachedOrigins)
{
    static const MockOriginData kData[] = {
        { "http://a.com/", kTemp, 1 },
        { "http://a.com:1/", kTemp, 20 },
        { "http://b.com/", kPerm, 300 },
        { "http://c.com/", kTemp, 4000 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    // TODO(kinuko): Be careful when we add cache pruner.

    std::set<GURL> origins;
    GetCachedOrigins(kTemp, &origins);
    EXPECT_TRUE(origins.empty());

    // No matter how we make queries the quota manager tries to cache all
    // the origins at startup.
    GetHostUsage("a.com", kTemp);
    base::RunLoop().RunUntilIdle();
    GetCachedOrigins(kTemp, &origins);
    EXPECT_EQ(3U, origins.size());

    GetHostUsage("b.com", kTemp);
    base::RunLoop().RunUntilIdle();
    GetCachedOrigins(kTemp, &origins);
    EXPECT_EQ(3U, origins.size());

    GetCachedOrigins(kPerm, &origins);
    EXPECT_TRUE(origins.empty());

    GetGlobalUsage(kTemp);
    base::RunLoop().RunUntilIdle();
    GetCachedOrigins(kTemp, &origins);
    EXPECT_EQ(3U, origins.size());

    for (size_t i = 0; i < arraysize(kData); ++i) {
        if (kData[i].type == kTemp)
            EXPECT_TRUE(origins.find(GURL(kData[i].origin)) != origins.end());
    }
}

TEST_F(QuotaManagerTest, NotifyAndLRUOrigin)
{
    static const MockOriginData kData[] = {
        { "http://a.com/", kTemp, 0 },
        { "http://a.com:1/", kTemp, 0 },
        { "https://a.com/", kTemp, 0 },
        { "http://b.com/", kPerm, 0 }, // persistent
        { "http://c.com/", kTemp, 0 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GURL origin;
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(eviction_origin().is_empty());

    NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("http://a.com/", eviction_origin().spec());

    NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
    NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
    NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("http://a.com/", eviction_origin().spec());

    DeleteOriginFromDatabase(eviction_origin(), kTemp);
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("https://a.com/", eviction_origin().spec());

    DeleteOriginFromDatabase(eviction_origin(), kTemp);
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("http://c.com/", eviction_origin().spec());
}

TEST_F(QuotaManagerTest, GetLRUOriginWithOriginInUse)
{
    static const MockOriginData kData[] = {
        { "http://a.com/", kTemp, 0 },
        { "http://a.com:1/", kTemp, 0 },
        { "https://a.com/", kTemp, 0 },
        { "http://b.com/", kPerm, 0 }, // persistent
        { "http://c.com/", kTemp, 0 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GURL origin;
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(eviction_origin().is_empty());

    NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
    NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
    NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
    NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);

    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("http://a.com/", eviction_origin().spec());

    // Notify origin http://a.com is in use.
    NotifyOriginInUse(GURL("http://a.com/"));
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("https://a.com/", eviction_origin().spec());

    // Notify origin https://a.com is in use while GetEvictionOrigin is running.
    GetEvictionOrigin(kTemp);
    NotifyOriginInUse(GURL("https://a.com/"));
    base::RunLoop().RunUntilIdle();
    // Post-filtering must have excluded the returned origin, so we will
    // see empty result here.
    EXPECT_TRUE(eviction_origin().is_empty());

    // Notify access for http://c.com while GetEvictionOrigin is running.
    GetEvictionOrigin(kTemp);
    NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    // Post-filtering must have excluded the returned origin, so we will
    // see empty result here.
    EXPECT_TRUE(eviction_origin().is_empty());

    NotifyOriginNoLongerInUse(GURL("http://a.com/"));
    NotifyOriginNoLongerInUse(GURL("https://a.com/"));
    GetEvictionOrigin(kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("http://a.com/", eviction_origin().spec());
}

TEST_F(QuotaManagerTest, GetOriginsModifiedSince)
{
    static const MockOriginData kData[] = {
        { "http://a.com/", kTemp, 0 },
        { "http://a.com:1/", kTemp, 0 },
        { "https://a.com/", kTemp, 0 },
        { "http://b.com/", kPerm, 0 }, // persistent
        { "http://c.com/", kTemp, 0 },
    };
    MockStorageClient* client = CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem);
    RegisterClient(client);

    GetOriginsModifiedSince(kTemp, base::Time());
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(modified_origins().empty());
    EXPECT_EQ(modified_origins_type(), kTemp);

    base::Time time1 = client->IncrementMockTime();
    client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10);
    client->ModifyOriginAndNotify(GURL("http://a.com:1/"), kTemp, 10);
    client->ModifyOriginAndNotify(GURL("http://b.com/"), kPerm, 10);
    base::Time time2 = client->IncrementMockTime();
    client->ModifyOriginAndNotify(GURL("https://a.com/"), kTemp, 10);
    client->ModifyOriginAndNotify(GURL("http://c.com/"), kTemp, 10);
    base::Time time3 = client->IncrementMockTime();

    GetOriginsModifiedSince(kTemp, time1);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(4U, modified_origins().size());
    EXPECT_EQ(modified_origins_type(), kTemp);
    for (size_t i = 0; i < arraysize(kData); ++i) {
        if (kData[i].type == kTemp)
            EXPECT_EQ(1U, modified_origins().count(GURL(kData[i].origin)));
    }

    GetOriginsModifiedSince(kTemp, time2);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(2U, modified_origins().size());

    GetOriginsModifiedSince(kTemp, time3);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(modified_origins().empty());
    EXPECT_EQ(modified_origins_type(), kTemp);

    client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10);

    GetOriginsModifiedSince(kTemp, time3);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1U, modified_origins().size());
    EXPECT_EQ(1U, modified_origins().count(GURL("http://a.com/")));
    EXPECT_EQ(modified_origins_type(), kTemp);
}

TEST_F(QuotaManagerTest, DumpQuotaTable)
{
    SetPersistentHostQuota("example1.com", 1);
    SetPersistentHostQuota("example2.com", 20);
    SetPersistentHostQuota("example3.com", 300);
    base::RunLoop().RunUntilIdle();

    DumpQuotaTable();
    base::RunLoop().RunUntilIdle();

    const QuotaTableEntry kEntries[] = {
        QuotaTableEntry("example1.com", kPerm, 1),
        QuotaTableEntry("example2.com", kPerm, 20),
        QuotaTableEntry("example3.com", kPerm, 300),
    };
    std::set<QuotaTableEntry> entries(kEntries, kEntries + arraysize(kEntries));

    typedef QuotaTableEntries::const_iterator iterator;
    for (iterator itr(quota_entries().begin()), end(quota_entries().end());
         itr != end; ++itr) {
        SCOPED_TRACE(testing::Message()
            << "host = " << itr->host << ", "
            << "quota = " << itr->quota);
        EXPECT_EQ(1u, entries.erase(*itr));
    }
    EXPECT_TRUE(entries.empty());
}

TEST_F(QuotaManagerTest, DumpOriginInfoTable)
{
    using std::make_pair;

    quota_manager()->NotifyStorageAccessed(
        QuotaClient::kUnknown,
        GURL("http://example.com/"),
        kTemp);
    quota_manager()->NotifyStorageAccessed(
        QuotaClient::kUnknown,
        GURL("http://example.com/"),
        kPerm);
    quota_manager()->NotifyStorageAccessed(
        QuotaClient::kUnknown,
        GURL("http://example.com/"),
        kPerm);
    base::RunLoop().RunUntilIdle();

    DumpOriginInfoTable();
    base::RunLoop().RunUntilIdle();

    typedef std::pair<GURL, StorageType> TypedOrigin;
    typedef std::pair<TypedOrigin, int> Entry;
    const Entry kEntries[] = {
        make_pair(make_pair(GURL("http://example.com/"), kTemp), 1),
        make_pair(make_pair(GURL("http://example.com/"), kPerm), 2),
    };
    std::set<Entry> entries(kEntries, kEntries + arraysize(kEntries));

    typedef OriginInfoTableEntries::const_iterator iterator;
    for (iterator itr(origin_info_entries().begin()),
         end(origin_info_entries().end());
         itr != end; ++itr) {
        SCOPED_TRACE(testing::Message()
            << "host = " << itr->origin << ", "
            << "type = " << itr->type << ", "
            << "used_count = " << itr->used_count);
        EXPECT_EQ(1u, entries.erase(make_pair(make_pair(itr->origin, itr->type), itr->used_count)));
    }
    EXPECT_TRUE(entries.empty());
}

TEST_F(QuotaManagerTest, QuotaForEmptyHost)
{
    GetPersistentHostQuota(std::string());
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(0, quota());

    SetPersistentHostQuota(std::string(), 10);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaErrorNotSupported, status());
}

TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleOrigin)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com/", kTemp, 2 },
    };
    static const MockOriginData kData3[] = {
        { "http://foo.com/", kTemp, 4 },
    };
    static const MockOriginData kData4[] = {
        { "http://foo.com/", kTemp, 8 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kAppcache);
    MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
        QuotaClient::kDatabase);
    MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
        QuotaClient::kIndexedDatabase);
    RegisterClient(client1);
    RegisterClient(client2);
    RegisterClient(client3);
    RegisterClient(client4);

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kFileSystem);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 1, usage());

    DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kAppcache);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());

    DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());

    DeleteOriginData(GURL("http://foo.com/"), kTemp,
        QuotaClient::kIndexedDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
}

TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleHost)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com:1111/", kTemp, 1 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com:2222/", kTemp, 2 },
    };
    static const MockOriginData kData3[] = {
        { "http://foo.com:3333/", kTemp, 4 },
    };
    static const MockOriginData kData4[] = {
        { "http://foo.com:4444/", kTemp, 8 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kAppcache);
    MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
        QuotaClient::kDatabase);
    MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
        QuotaClient::kIndexedDatabase);
    RegisterClient(client1);
    RegisterClient(client2);
    RegisterClient(client3);
    RegisterClient(client4);

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    DeleteHostData("foo.com", kTemp, QuotaClient::kFileSystem);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 1, usage());

    DeleteHostData("foo.com", kTemp, QuotaClient::kAppcache);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());

    DeleteHostData("foo.com", kTemp, QuotaClient::kDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());

    DeleteHostData("foo.com", kTemp, QuotaClient::kIndexedDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
}

TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleOrigin)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com/", kTemp, 1 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com/", kTemp, 2 },
    };
    static const MockOriginData kData3[] = {
        { "http://foo.com/", kTemp, 4 },
    };
    static const MockOriginData kData4[] = {
        { "http://foo.com/", kTemp, 8 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kAppcache);
    MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
        QuotaClient::kDatabase);
    MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
        QuotaClient::kIndexedDatabase);
    RegisterClient(client1);
    RegisterClient(client2);
    RegisterClient(client3);
    RegisterClient(client4);

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    DeleteOriginData(GURL("http://foo.com/"), kTemp,
        QuotaClient::kFileSystem | QuotaClient::kDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage());

    DeleteOriginData(GURL("http://foo.com/"), kTemp,
        QuotaClient::kAppcache | QuotaClient::kIndexedDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
}

TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleHost)
{
    static const MockOriginData kData1[] = {
        { "http://foo.com:1111/", kTemp, 1 },
    };
    static const MockOriginData kData2[] = {
        { "http://foo.com:2222/", kTemp, 2 },
    };
    static const MockOriginData kData3[] = {
        { "http://foo.com:3333/", kTemp, 4 },
    };
    static const MockOriginData kData4[] = {
        { "http://foo.com:4444/", kTemp, 8 },
    };
    MockStorageClient* client1 = CreateClient(kData1, arraysize(kData1),
        QuotaClient::kFileSystem);
    MockStorageClient* client2 = CreateClient(kData2, arraysize(kData2),
        QuotaClient::kAppcache);
    MockStorageClient* client3 = CreateClient(kData3, arraysize(kData3),
        QuotaClient::kDatabase);
    MockStorageClient* client4 = CreateClient(kData4, arraysize(kData4),
        QuotaClient::kIndexedDatabase);
    RegisterClient(client1);
    RegisterClient(client2);
    RegisterClient(client3);
    RegisterClient(client4);

    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    const int64_t predelete_foo_tmp = usage();

    DeleteHostData("foo.com", kTemp,
        QuotaClient::kFileSystem | QuotaClient::kAppcache);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());

    DeleteHostData("foo.com", kTemp,
        QuotaClient::kDatabase | QuotaClient::kIndexedDatabase);
    base::RunLoop().RunUntilIdle();
    GetHostUsage("foo.com", kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
}

TEST_F(QuotaManagerTest, GetUsageAndQuota_Incognito)
{
    ResetQuotaManager(true);

    static const MockOriginData kData[] = {
        { "http://foo.com/", kTemp, 10 },
        { "http://foo.com/", kPerm, 80 },
    };
    RegisterClient(CreateClient(kData, arraysize(kData),
        QuotaClient::kFileSystem));

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(80, usage());
    EXPECT_EQ(0, quota());

    SetTemporaryGlobalQuota(100);
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_LE(std::min(static_cast<int64_t>(100 / kPerHostTemporaryPortion),
                  QuotaManager::kIncognitoDefaultQuotaLimit),
        quota());

    mock_special_storage_policy()->AddUnlimited(GURL("http://foo.com/"));
    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(80, usage());
    EXPECT_EQ(QuotaManager::kIncognitoDefaultQuotaLimit, quota());

    GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(kQuotaStatusOk, status());
    EXPECT_EQ(10, usage());
    EXPECT_EQ(QuotaManager::kIncognitoDefaultQuotaLimit, quota());
}

TEST_F(QuotaManagerTest, GetVolumeInfo)
{
    // We aren't actually testing that it's correct, just that it's sane.
    base::FilePath tmp_dir;
    ASSERT_TRUE(base::GetTempDir(&tmp_dir));
    uint64_t available_space = 0;
    uint64_t total_size = 0;
    EXPECT_TRUE(GetVolumeInfo(tmp_dir, &available_space, &total_size));
    EXPECT_GT(available_space, 0u) << tmp_dir.value();
    EXPECT_GT(total_size, 0u) << tmp_dir.value();
}

} // namespace content
